「Go框架」go中的平滑关闭究竟是怎么关闭的

您所在的位置:网站首页 f5 平滑退出 「Go框架」go中的平滑关闭究竟是怎么关闭的

「Go框架」go中的平滑关闭究竟是怎么关闭的

2023-08-27 02:48| 来源: 网络整理| 查看: 265

大家好,我是渔夫子。本号新推出「Go工具箱」系列,意在给大家分享使用go语言编写的、实用的、好玩的工具。同时了解其底层的实现原理,以便更深入地了解Go语言。

关闭软件可以分为平滑关闭(软关闭)和硬关闭。就像我们在关闭电脑的时候,有时候遇到电脑死机,会直接长按开关键,直至电脑关机,这就是硬关机。 而通过电脑上的菜单选择“关机”,则属于软关机(平滑关闭)。在软关机的时候,大家应该会注意到时间会比较长,时不时还会有弹窗弹出 询问是否要退出。

在我们自己编写的web应用中,实际上也是需要有软关闭的。今天我就golang中的gin框架为例,来聊聊平滑关闭背后的处理逻辑。

image.png

一、为什么平滑关闭如此重要性?

硬关闭就是当程序收到关闭的信号后,立即将正在做的事情关闭。比如,我们在强制关机时,代码还没有保存,就会造成丢失。 而平滑关闭具有如下优点:

第一,平滑关闭能够及时的释放资源。 第二,平滑关闭能够保证事务的完整性。

比如在web服务中,一个请求还没有返回,就被关闭了,那么影响体验。平滑关闭,能够等待请求处理完成后 连接再被关闭。 所以,平滑关闭本质上就是当程序收到关闭的信号后,会等待程序把正在做的事情做完,释放掉所有的资源后再关闭服务。

二、web服务是如何接收和处理请求的?

无论是正常关闭还是平滑关闭服务,本质上都是关闭服务的资源。所以,有必要先了解下web服务是如何启动的以及启动后是如何处理http请求。这样在关闭的时候就能对应的知道应该关闭哪些资源以及如何关闭了。

我们以gin框架为例来说明处理http请求的流程。

先构建一个server对象 根据传入的网络地址,建立网络监听器listener。实际是建立了一个socket。 将listener加入到server对象的一个资源池中,以代表server监听正在使用的listener资源。 listner开始监听对应网络地址上(socket)的请求。 当有用户发起http请求时,Accept函数就能监听到。 对新接收的请求创建一个一个TCP连接 将新的TCP连接包装成一个conn对象,同时将该conn对象加入到server的关羽conn的资源池中。这样server就能跟踪当前有多少个请求连接正在处理请求。 启动一个新的协程,异步处理该连接 读取请求内容 执行具体的处理逻辑 输出响应 请求结束,关闭本次的TCP连接。同时,从资源池中释放掉对应的conn资源。 继续回到Accept监听后续的HTTP请求。

image.png

通过以上流程图,我们实际上可以将web server处理http请求的整个过程分为两部分:创建网络监听器listener(socket)阶段以及监听并处理HTTP请求阶段。 相应的,和这两个阶段相对应的使用到的资源就是网络监听器listner以及每个HTTP请求连接conn。即上图中的server中的两个资源池。

对于两种资源,分别有存在不同的状态。下面我们简单看下两种资源的各自状态以及转换。

listener资源的状态

listner的作用就是监听网络连接。所以该资源有两种状态:正常和关闭状态。

conn资源的状态

conn本质上是一个TCP的连接,但server对象为了容易跟踪目前监听到的连接,所以将TCP连接包装成了conn,并给conn定义了以下状态:新连接(New)、活跃状态(Active)、关闭状态(Closed)、空闲状态(Idle)和被劫持状态(Hijacked)。 以下是各状态之间的转化关系: image.png

在启动阶段是建立资源。那么,在server的关闭阶段,主要也就是要释放这些资源。那么该如何释放这些资源就是我们接下来要讨论的重点。

三、直接关闭web服务是关闭了什么?

在web框架中,server的Close函数对应功能就是直接关闭server服务。在gin框架中,对应的代码如下: image.png

从代码中可以看到,基本上是首先是给server对象设置关闭的标志位;然后关闭doneChan;关闭所有的listener资源,以停止接收新的连接;最后,循环conn资源,依次关闭。

image.png

这里有一点需要注意,在关闭conn资源的时候,不管conn当前处于什么状态,都是立即关闭。也就是说如果一个conn正处于Active状态,代表着该请求还没处理完,那么也会立即终止处理。对于客户端的表现来说 就是收到“连接被拒绝,网站无法访问”的错误。

我们实验一下。如下代码是注册了路由"/home",在处理函数中我们等待了5秒,以便模拟在关闭的时候,我们我们的请求处理还没有完成的场景。然后,往下就是通过signal.Notify函数在quit通道上注册了程序终止的信号os.Interrupt(即按Ctrl+C),当通过在终端上按Ctrl+C发送了中断信号给quit通道时,就执行server.Close()函数。如下代码:

package main import ( "context" "log" "net/http" "os" "os/signal" "time" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() router.GET("/home", func(c *gin.Context) { time.Sleep(5 * time.Second) c.String(http.StatusOK, "Welcome Gin Server") }) server := &http.Server{ Addr: ":8080", Handler: router, } quit := make(chan os.Signal) signal.Notify(quit, os.Interrupt) server.RegisterOnShutdown(func(){ log.Println("start execute out shutown") }) go func() { if err := server.ListenAndServe(); err != nil { if err == http.ErrServerClosed { log.Println("Server closed under request") } else { log.Fatal("Server closed unexpect") } } }()


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3